package project3;

import java.io.IOException;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.List;
import java.util.Map;
import java.util.Random;
import java.util.concurrent.ConcurrentHashMap;

import project1.FetchResponse;
import project1.IResponse;
import project1.MainClass;
import project1.ServiceRegister;

public class SourceRouter {
	private static Random ran;
	private int port; // The port listenning the incoming request from browser.
	private static Map<Integer, String> routerList;
	private static final int MAX_RELAY_CREATE = 3;
	
	// key << 16 is the connected router id.
	private static Map<Integer, String> circuitToRouterList;
	private static Map<String, Integer> routerToCircuitList;
	private Integer streamID;
	private ServerSocket socket;
	private static ServiceRegister register;
	private static String routerPrefix;
	
	public SourceRouter(int port, String ip, String p, String routerid, String routerData) throws Exception {
		if (routerList == null) {
			this.ran = new Random();
			routerList = new ConcurrentHashMap<Integer, String>();
			circuitToRouterList = new ConcurrentHashMap<Integer, String>();
			routerToCircuitList = new ConcurrentHashMap<String, Integer>();
			this.streamID = 0;
			this.socket = new ServerSocket(port);
			String[] routerIdArray = routerid.split("-");
			this.routerPrefix = routerIdArray[0] + "-" + routerIdArray[1];
			
			// Get the register for the registration agency.
			MainClass.main(new String[]{ip, p});
			register = MainClass.getRegister();
			
			// Register this current Node.
			IResponse res = register.sendRegister(routerid, "" + port, "" + routerData);
			if (res == null || res.getType() != 0x02) {
				throw new IllegalStateException("Registration failure.");
			}
		}
	}
	
	/**
	 * Listen the request from the browser. Create a new socket thread to handle new connection.
	 * 
	 * throw Exception if the port is not successfully bind.
	 * 
	 */
	public void listenBrowserRequest() { 
		new Thread() {
			
			@Override
			public void run() {
				while (true) {
					try {
						Socket soc = socket.accept();
						BrowserFacingThread thread = new BrowserFacingThread(soc);
						thread.start();
					} catch (IOException e) {
						//TODO: Handle the error when browser connection to the server error
					}
				}
			}
		}.run();
	}
	
	/**
	 * Fetch new active router tables and refresh the routerList. Create a new circuit. 
	 * If failure, try multiple times (to the same router then to other routers).
	 * TODO: Figure out a way to handle the case when creation always fails. (To user and to router)
	 * 
	 * @return circuit id
	 */
	private static int createCircuit(String ipPort) {
		
		return -1;
	}
	
	/**
	 * Choose a random circuit. If circuit does not exist, create a new circuit.
	 * 
	 * @return
	 */
	public static int chooseCircuit() {
		IResponse res = register.sendFetch(routerPrefix);
		if (res.getClass() != FetchResponse.class) {
			throw new IllegalStateException("Fail to get the fecth response");
		}
		FetchResponse fres = (FetchResponse) res;
		List<String[]> services = fres.getOtherService();
		synchronized (routerList) {
			// TODO: Can check whether there are routers not in new router list but in the old one.
			//		 Do some ciruit removing about the old routers
			routerList.clear();		
			for (String[] router : services) {
				for (int i = 0; i < router.length; i++) {
					String ip = router[0];
					String port = router[1];
					int rid = (Integer.parseInt(router[2]) & 0xFFFF);
					routerList.put(rid, ip + ":" + port);
				}
			}
		}
		
		Integer[] routerIdList = (Integer[]) routerList.keySet().toArray();
		int nextRouterId = routerIdList[ran.nextInt(routerIdList.length)];
		String nextRouterIpPort = routerList.get(nextRouterId);
		int cirId = routerToCircuitList.get(nextRouterIpPort);
		if (!routerToCircuitList.containsKey(nextRouterIpPort)) {
			cirId = createCircuit(routerList.get(nextRouterIpPort));
		}

		return cirId;
	}
	
	/**
	 * Create a new unique stream id.
	 * Synchornized.
	 */
	public static int generateNextStreamId() {
		return -2;
	}
	
	// TODO: 
	public static void setResponse(Cell cell) {
		
	}
}
